import csv
import simplejson
import os
import urllib
import cgi

class CoverageReport:
  def __init__(self):
    # PureCoverage Module rows
    #
    #               Name                 Type                                   Functions Functions Total     % Functions Lines  Lines Total % Lines          Calls
    #                                                                           Missed    Hit       Functions Hit         Missed Hit   Lines Hit
    #
    # 0             1                    2          3          4                5         6         7         8           9      10    11    12       13      14
    # CoverageData  C:\...bin\NSPR4.DLL  Module     <none>     <none>           543       316       859       36.79       9078   3439  12517 27.47    <none>  35516921
    self.modules = []

    # PureCoverage Directory rows
    #
    #               Name                 Type                  Module           Functions Functions Total     % Functions Lines  Lines Total % Lines          Calls
    #                                                          Path             Missed    Hit       Functions Hit         Missed Hit   Lines Hit
    #
    # 0             1                    2          3          4                5         6         7         8           9      10    11    12       13      14
    # CoverageData  c:\...\xpcom\glue    Directory  <none>     ...\tdynamic.dll 145       20        165       12.12       791    93    884   10.52    <none>  33
    #
    # A single directory can appear in multiple modules.
    self.dirs = []

    # PureCoverage SourceFile row
    #
    #               Name                 Type       Parent     Module           Functions Functions Total     % Functions Lines  Lines Total % Lines          Calls
    #                                               Path       Path             Missed    Hit       Functions Hit         Missed Hit   Lines Hit
    #  
    # 0             1                    2          3          4                5         6         7         8           9      10    11    12       13      14
    # CoverageData  c:\...\nss\nssck.api SourceFile c:\nss\    ...\nssckbi.dll  55        14        69        20.29       165    43    208   20.67    <none>  18
    self.source_files = []

    # PureCoverage Function row
    #
    #               Name                 Type       Parent     Module           Functions Functions Total     % Functions Lines  Lines Total % Lines  Line    Calls
    #                                               Path       Path             Missed    Hit       Functions Hit         Missed Hit   Lines Hit      Number
    #   
    # 0             1                    2          3          4                5         6         7         8           9      10    11    12       13      14
    # CoverageData  ...Release(void)     Function   c:\foo.cpp ...\layout.dll   1         0         1         0.0         1      0     1     0.00     281     0
    self.functions = []

    # PureCoverage SourceLines
    #
    # Line numbers can repeat, for lines with multiple blocks and macros.
    # In LineNumber, I'm not sure what the number after the decimal
    # point means.
    #
    # SourceLines  f:\...\eh\hooks.cpp  C:\...\MSVCR80D.DLL
    #       LineNumber      LineCoverage
    #       74.75           1
    #       80.75           1
    #       80.75           1
    #       81.75           1
    #       94.75           0
    #       98.75           0
    #       98.75           0
    #       100.75          0
    #
    # SourceLines...
    self.source_lines = {}
    self.currentSource = None

  def __processCoverageDataRow(self, row):
    # A list of field names and type coercion functions
    rowTransforms = [
      ("purpose", None),
      ("name", None),
      ("type", None),
      ("parent_path", None),
      ("module_path", None),
      ("functions_missed", int),
      ("functions_hit", int),
      ("total_functions_hit", int),
      ("percent_functions_hit", float),
      ("lines_missed", int),
      ("lines_hit", int),
      ("total_lines", int),
      ("percent_lines_hit", float),
      ("line_number", int),
      ("calls", int)
    ]
      
    # now stuff the coerced values into a dict
    d = {}
    for i in range(len(rowTransforms)):
      val = row[i]
      fCoerce = rowTransforms[i][1]
      if val == "<none>":
        val = None
      elif fCoerce:
        val = fCoerce(val)
      d[rowTransforms[i][0]] = val

    return d

  def __consume(self, row):
    """Process a list of values from the purify tab separated file"""
    if len(row) == 15 and row[0] == "CoverageData" and row[2] == "Module":
      self.modules.append(self.__processCoverageDataRow(row))
    elif len(row) == 15 and row[0] == "CoverageData" and row[2] == "Directory":
      self.dirs.append(self.__processCoverageDataRow(row))
    elif len(row) == 15 and row[0] == "CoverageData" and row[2] == "SourceFile":
      self.source_files.append(self.__processCoverageDataRow(row))
    elif len(row) == 15 and row[0] == "CoverageData" and row[2] == "Function":
      self.functions.append(self.__processCoverageDataRow(row))
    elif len(row) == 3 and row[0] == "SourceLines":
      self.currentSource = row[1]
      self.source_lines[row[1]] = {"module_name":row[2], "lines":{}}
    elif self.currentSource is not None and len(row) == 3 and row[1] != "LineNumber":
      num, mystery = row[1].split(".")
      linedata = self.source_lines[self.currentSource]["lines"]
      coverage = int(row[2])
      # lines can repeat, in which case they are macros or lines with multiple blocks
      if (not linedata.has_key(num)) or linedata[num] < coverage:
        linedata[num] = coverage
        
  def process(self, fh):
    """Populate with data contained in file handle fh"""
    csvreader = csv.reader(fh, delimiter="\t")
    for row in csvreader:
      self.__consume(row)
    self.inSourceLines = None

def outputCodeMarkup(path, outputDir, report):
  filename = urllib.quote(path) + ".html"
  sourcef = open(path)
  htmlf = open(os.path.join(outputDir, filename), "w")
  linedata = report.source_lines[path]["lines"]
  htmlf.write("<html><head><title>%s</title>" % cgi.escape(path))
  htmlf.write("<link rel='stylesheet' href='../../source.css'>")
  htmlf.write("</head><body>")
  htmlf.write("<table>");
  num = 1
  for line in sourcef:
    cov = linedata.get(str(num), None)
    if cov is None:
      htmlf.write("<tr><td></td><td><pre>%d</pre></td><td class='line'><pre>%s</pre></td></tr>" % (num, cgi.escape(line[:-1])))
    elif cov == 0:
      htmlf.write("<tr class='miss'><td><pre>%d</pre></td><td><pre>%d</pre></td><td class='line'><pre>%s</pre></td></tr>" % (cov, num, cgi.escape(line[:-1])))
    else:
      htmlf.write("<tr class='hit'><td><pre>%d</pre></td><td><pre>%d</pre></td><td class='line'><pre>%s</pre></td></tr>" % (cov, num, cgi.escape(line[:-1])))          
    num += 1
  htmlf.write("</table></body></html>")
  sourcef.close()
  htmlf.close()
  return filename

def outputReportAsJSON(report, outputDir):
  dirs = set([dir["name"] for dir in report.dirs])
  # write the top of our tree
  fh = open(os.path.join(outputDir, "dirlist.json"), "w")
  fh.write("addDirs(")
  simplejson.dump([x for x in dirs], fh)
  fh.write(");")
  fh.close()

  # create a directory to store highlighted source files
  codeDir = os.path.join(outputDir, "code")
  if not os.path.exists(codeDir):
    os.mkdir(codeDir)
    
  # write out a JSON file for each directory
  for name in dirs:
    sources = [data for data in report.source_files if
               data["parent_path"] == name]
    for source in sources:
      if (os.path.exists(source["name"])):
        filename = outputCodeMarkup(source["name"], codeDir, report)
        source["html"] = "<code>(%6.2f%% of lines) <a href='data/code/%s'>%s</a></code>" % (source["percent_lines_hit"],
                                                                                            urllib.quote(filename),
                                                                                            source["name"])
      else:
        source["html"] = "<code>(%6.2f%% of lines) %s</code>" % (source["percent_lines_hit"],  source["name"])

    fh = open(os.path.join(outputDir,
                           urllib.quote(name) + ".json"), "w")
    simplejson.dump(sources, fh);
    fh.close()
                           
    
